#include <vector>
#include <list>
#include <string>
#include "Graphic.h"

using namespace std;

Graphic::Graphic()
{
    this->showControls  = true;
    this->shader        = NULL;
    this->isCurveClosed = false;
    color = vec3d(0.0, 0.0, 0.0);

    this->curve = new BSplineCurve();
    this->curve->setIncrement(0.01);
    this->curve->setOrder(3);
}

Graphic::~Graphic()
{
}

void Graphic::controlUpdated(Vertex* changedVertex)
{
    vec<Point3D*>* controlPoints;
    vec<Point3D*>::iterator pIter;
    vec3d* vertex;
    vec3d pos;

    // update the non-midpoint control points
    controlPoints = this->curve->getControlPoints();
    pIter = controlPoints->begin();
    do
    {
        if (!(*pIter)->isMidpoint)
        {
            vertex = (vec3d*)((*pIter)->vertex);
            pos = (*vertex);
            this->curve->movePoint((*pIter), (pos)[0], (pos)[1], (pos)[2]);
        }

        pIter++;
    }
    while(pIter != controlPoints->end());

    // update the midpoint control points
    /*pIter = controlPoints->begin();
    do
    {
        if ((*pIter)->isMidpoint)
        {
            pIter--;
            vertex = (vec3d*)((*pIter)->vertex);
            pos = 0.5*(*vertex);
            pIter += 2;
            vertex = (vec3d*)((*pIter)->vertex);
            pos += 0.5*(*vertex);
            pIter--;
            this->curve->movePoint((*pIter), (pos)[0], (pos)[1], (pos)[2]);
        }

        pIter++;
    }
    while(pIter != controlPoints->end());*/
}

void Graphic::loadShader(string filename)
{
    string fragFileName;
    string vertFileName;

    fragFileName = filename + ".frag";
    vertFileName = filename + ".vert";
    GLenum err = glewInit();
	if (GLEW_OK == err)
	{	
        this->shader = new Shader(&fragFileName[0], &vertFileName[0]);
	}
}

/**
    Performs the finalization process for a closed curve, which includes
    calculating the convex/concave masks.
*/
void Graphic::close()
{
    const unsigned x = 0, y = 1, z = 2;

    bool        isInside;
    bool        prevInside;
    bool        isFlipped;
    vec3d       testPoint;
    vec<vec3d>  testPolygon;

    list<Vertex>::iterator vIter;
    list<Vertex>::iterator nextIter;
    list<Vertex>::iterator prevIter;

    // generate the polygon to test against
    vIter       = this->points.begin();
    nextIter    = this->points.begin(); nextIter++;
    do
    {
        testPolygon.push_back((*vIter).prevOffCurvePoint);
        testPolygon.push_back((*vIter).onCurvePoint);

        // skip the point that we will be testing
        if (vIter != this->points.begin())
        {
            testPolygon.push_back((*vIter).nextOffCurvePoint);
        }

        // loop around if at the end of the curve
        if (nextIter == this->points.end())
        {
            nextIter = this->points.begin();
        }

        testPolygon.push_back(0.5*(*nextIter).prevOffCurvePoint + 
                              0.5*(*vIter).nextOffCurvePoint);

        vIter++;
        nextIter++;
    }
    while(vIter != this->points.end());

    // test the NEXT offcurve point of the first vertex
    testPoint = (*this->points.begin()).nextOffCurvePoint;
    isInside = isInsideCurve(testPoint, testPolygon, z);

    // set the inside mask for this point
    (*this->points.begin()).insideMask[1] = true;
    (*this->points.begin()).insideMask[2] = isInside;
    prevInside = isInside;

    // test remaining points for inside/outside
    prevIter    = this->points.begin(); 
    vIter       = prevIter; vIter++;
    nextIter    = vIter; nextIter++;
    do
    {   
        // loop around if at the end of the curve
        if (nextIter == this->points.end())
        {
            nextIter = this->points.begin();
        }

        // test the PREVIOUS offcurve point
        isFlipped = isRayIntersected((*vIter).prevOffCurvePoint, 
                                     (*prevIter).nextOffCurvePoint,
                                     (*vIter).onCurvePoint,
                                     (*prevIter).onCurvePoint);

        isInside = ((prevInside && !isFlipped) || 
                    (!prevInside && isFlipped));

        (*vIter).insideMask[0] = isInside;  // the previous offcurve point
        (*vIter).insideMask[1] = true;
        prevInside = isInside;

        // test the NEXT offcurve point
        isFlipped = isRayIntersected((*vIter).nextOffCurvePoint,
                                     (*vIter).prevOffCurvePoint,
                                     0.5*(*vIter).nextOffCurvePoint + 0.5*(*nextIter).prevOffCurvePoint,
                                     0.5*(*prevIter).nextOffCurvePoint + 0.5*(*vIter).prevOffCurvePoint);

        isInside = ((prevInside && !isFlipped) || 
                    (!prevInside && isFlipped));

        (*vIter).insideMask[2] = isInside;  // the next offcurve point
        prevInside = isInside;

        prevIter++;
        vIter++;
        nextIter++;
    }
    while(vIter != this->points.end());

    // test the PREVIOUS offcurve point of the first vertex
    vIter = this->points.begin();
    isFlipped = isRayIntersected((*vIter).prevOffCurvePoint, 
                                 (*prevIter).nextOffCurvePoint,
                                 (*vIter).onCurvePoint,
                                 (*prevIter).onCurvePoint);

    isInside = ((prevInside && !isFlipped) || 
                (!prevInside && isFlipped));

    (*vIter).insideMask[0] = isInside;  // the previous offcurve point

    // generate the inner polygon
    generateInnerPolygon();

    this->isCurveClosed = true;
    this->showControls  = false;
}

void Graphic::generateInnerPolygon()
{
    PolyMesh& inner = this->innerMesh;
    vec3d   midpoint;
    vec3d   tmp;
    vec<VertexHandle> vertices;

    PolyMesh::ConstVertexIter vMeshIter;
    PolyMesh::ConstFaceIter fMeshIter;
    list<Vertex>::iterator vIter;
    list<Vertex>::iterator nextIter;
    
    // first we create a massive polygon
    vIter = this->points.begin();
    nextIter = vIter; nextIter++;
    do
    {
        if (nextIter == this->points.end())
        {
            nextIter = this->points.begin();
        }

        // render prev off curve if its inside
        if ((*vIter).insideMask[0])
        {
            tmp = (*vIter).prevOffCurvePoint;
            inner.add_vertex(PolyMesh::Point(tmp[0], tmp[1], tmp[2]));
        }

        // always render on curve point
        tmp = (*vIter).onCurvePoint;
        inner.add_vertex(PolyMesh::Point(tmp[0], tmp[1], tmp[2]));

        // render next off curve if its inside
        if ((*vIter).insideMask[2])
        {
            tmp = (*vIter).nextOffCurvePoint;
            inner.add_vertex(PolyMesh::Point(tmp[0], tmp[1], tmp[2]));
        }

        // always render next midpoint
        midpoint = 0.5*(*vIter).nextOffCurvePoint + 
                   0.5*(*nextIter).prevOffCurvePoint;
        inner.add_vertex(PolyMesh::Point(midpoint[0], midpoint[1], midpoint[2]));

        vIter++;
        nextIter++;
    }
    while(vIter != this->points.end());

    vMeshIter = inner.vertices_begin();
    do
    {
        vertices.push_back(vMeshIter);
        ++vMeshIter;
    }
    while(vMeshIter != inner.vertices_end());
    inner.add_face(vertices);

    // now lets triangulate what we created
}

/**
    Adds a new vertex to the curve and returns a pointer to it.
*/
Vertex* Graphic::addPoint(vec3d point)
{
    Point3D splinePoint;
    Vertex vert;
    vec3d temp;
    Vertex* lastVertex;

    // we need the last vertex for later
    if (this->points.size()  > 0)
    {
        lastVertex = &this->points.back();
    }

    vert.onCurvePoint = point;
    vert.nextOffCurvePoint = point;
    vert.prevOffCurvePoint = point;
    this->points.push_back(vert);

    // if 1+ spline points...
    if (this->curve->getControlPoints()->size()  > 0)
    {
        // add next offcurve of last vertex
        temp = lastVertex->nextOffCurvePoint;
        splinePoint = Point3D(temp[0], temp[1], temp[2]);
        splinePoint.vertex = (void*)&lastVertex->nextOffCurvePoint;
        this->curve->addControlPoint(splinePoint);

        // add midpoint
        /*temp = 0.5*lastVertex->nextOffCurvePoint + 0.5*vert.prevOffCurvePoint;
        splinePoint = Point3D(temp[0], temp[1], temp[2]);        
        splinePoint.isMidpoint = true;
        this->curve->addControlPoint(splinePoint);*/

        // add prev offcurve of this vertex
        temp = vert.prevOffCurvePoint;
        splinePoint = Point3D(temp[0], temp[1], temp[2]);
        splinePoint.vertex = (void*)&(this->points.back().prevOffCurvePoint);
        this->curve->addControlPoint(splinePoint);
    }

    // always add current oncurve point
    splinePoint = Point3D(vert.onCurvePoint[0],
                          vert.onCurvePoint[1],
                          vert.onCurvePoint[2]);
    splinePoint.vertex = (void*)&(this->points.back().onCurvePoint);
    this->curve->addControlPoint(splinePoint);

    return &this->points.back();
}

/**	Determines whether the specified test point is inside (returns true) or
 *	outside (returns false) the specified closed 2D curve. To ensure a 2D
 *	test, the ignoredAxis specified is ignored (0 = x, 1 = y, 2 = z).
 */
bool Graphic::isInsideCurve(const vec3d& testPoint, 
							const vec<vec3d> polygon, 
							int ignoredAxis)
{
	const unsigned x = 0, y = 1, z = 2;

	int				i, j;				// counter variables
	int				intersectCount;
	int				p1Index;			// index of first point in a line
	int				p2Index;		    // index of second point in a line
	int				axis1, axis2;		// the two non-ignored axis
	double			rayCoefficient;		// place on ray where line intersects
	double			lineCoefficient;	// place on line where ray intersects
	double			fullLineLength;		// length of line between two points
	double			minLineX;			// smallest x value of a line
	double			minLineY;			// smallest y value of a line
	vec3d			rayDirection;
	vec3d			rayOrigin;
	vector<vec3d>	polyPoints;

	// first lets "un-const" the const parameters
	polyPoints = polygon;
	rayOrigin = testPoint;	

	// create a random ray from the test point and assign
	// the two non-ignored axis
	axis1 = -1;
	axis2 = -1;
	for (j = x; j <= z; j++)
	{
		if (j != ignoredAxis)
		{
			rayDirection[j] = 1;  // any value will do here

			if (axis1 == -1)		{ axis1 = j; }
			else if (axis2 == -1)	{ axis2 = j; }
		}
		else
		{
			rayDirection[j] = 0;
		}
	}
    rayDirection = vmath::normalize(rayDirection);

	// shortening some variable names (you'll see why)
	vec3d	Vr = rayDirection;
	double&	LC = lineCoefficient;

	// check for intersection with every edge in the polygon
	intersectCount = 0;
	for (i = 0; i < polyPoints.size(); i++)
	{
		p1Index = i;
		p2Index = (i+1) % polyPoints.size(); // wraps around end of vector

		// again, just shortening variable names
		vec3d P0 = polyPoints[p1Index];
		vec3d P1 = polyPoints[p2Index];
		vec3d Pr = rayOrigin;

		// shifting the line to the +/+ quadrant so that the
		// upcoming denominator can never be zero
		minLineX = min(P0[axis1], P1[axis1]);
		minLineY = min(P0[axis2], P1[axis2]);
		if (minLineX < 0)
		{
			P0[axis1] += -minLineX;
			P1[axis1] += -minLineX;
			Pr[axis1] += -minLineX;
		}
		if (minLineY < 0)
		{
			P0[axis2] += -minLineY;
			P1[axis2] += -minLineY;
			Pr[axis2] += -minLineY;
		}
		if (P0[axis1] - P1[axis1] == P0[axis2] - P1[axis2])
		{
			P0[axis2] += 1;
			P1[axis2] += 1;
			Pr[axis2] += 1;
		}

		// calculating the line coefficient
		lineCoefficient = (Vr[axis1]*(P1[axis2] - Pr[axis2]) - Vr[axis2]*(P1[axis1] - Pr[axis1])) /
			              (Vr[axis2]*(P0[axis1] - P1[axis1]) - Vr[axis1]*(P0[axis2] - P1[axis2]));

		// calculating the ray coefficient
		// (recall: LC is lineCoefficient)
		rayCoefficient = (LC*P0[axis1] + (1 - LC)*P1[axis1] - Pr[axis1]) / Vr[axis1];

		// check if intersection conditions are satisfied
		if (rayCoefficient >= 0  &&  
			lineCoefficient > 0  &&
			lineCoefficient <= 1)
		{
			intersectCount++;  // woah! an intersection
		}
	}

	return (intersectCount % 2 != 0);  // odd => true, even => false
}

bool Graphic::isRayIntersected(vec3d P0, // one point on the line
                               vec3d P1, // another point on the line
                               vec3d R1, // origin of the test ray
                               vec3d R2) // destination of the test ray
{
    const int axis1 = 0, axis2 = 1;

    bool    result;
    double	minLineX;			// smallest x value of a line
	double	minLineY;			// smallest y value of a line
    double	rayCoefficient;		// place on ray where line intersects
	double	lineCoefficient;	// place on line where ray intersects
    double  maxRaySize; 
    vec3d   Pr;   // test ray origin
    vec3d   Vr;   // test ray direction

    // assume there is no intersection
    result = false;

    // set up some required variables
    Pr          = R1;
    Vr          = R2 - R1;
    maxRaySize  = vmath::length(Vr);
    Vr          = vmath::normalize(Vr);

    // shifting the line to the +/+ quadrant so that the
	// upcoming denominator can never be zero
	minLineX = min(P0[axis1], P1[axis1]);
	minLineY = min(P0[axis2], P1[axis2]);
	if (minLineX < 0)
	{
		P0[axis1] += -minLineX;
		P1[axis1] += -minLineX;
		Pr[axis1] += -minLineX;
	}
	if (minLineY < 0)
	{
		P0[axis2] += -minLineY;
		P1[axis2] += -minLineY;
		Pr[axis2] += -minLineY;
	}
	if (P0[axis1] - P1[axis1] == P0[axis2] - P1[axis2])
	{
		P0[axis2] += 1;
		P1[axis2] += 1;
		Pr[axis2] += 1;
	}

	// calculating the line coefficient
	lineCoefficient = (Vr[axis1]*(P1[axis2] - Pr[axis2]) - Vr[axis2]*(P1[axis1] - Pr[axis1])) /
		              (Vr[axis2]*(P0[axis1] - P1[axis1]) - Vr[axis1]*(P0[axis2] - P1[axis2]));

	// calculating the ray coefficient
	double&	LC      = lineCoefficient;  // shorthand var name
	rayCoefficient  = (LC*P0[axis1] + (1 - LC)*P1[axis1] - Pr[axis1]) / Vr[axis1];

	// check if intersection conditions are satisfied
    if (rayCoefficient <= maxRaySize && 
        rayCoefficient >= 0.0)
	{
		return true;
	}

    return false;
}